﻿using System;
using System.Linq;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;

namespace FoundationLib.Common.Collections
{
	/// <summary>
	/// Represents a boxed set which can be used as a NHibernate &lt;component&gt; element mapping.
	/// It is therefore merely a proxy to the underlying <see cref="IList{T}"/> instance. NHibernate
	/// uses an instance of this type for lazy loading purposes.
	/// </summary>
	/// <typeparam name="T">The type of the set elements</typeparam>
	public class BoxedCollection<T> : IList<T>, INotifyCollectionChanged
	{
		/// <summary>
		/// The underlying set containing the boxed elements
		/// </summary>
		private IList<T> elements;
		private DuplicateAction duplicateAction;
		private NotifyCollectionChangedEventHandler collectionChanged;

		/// <summary>
		/// Gets or sets the underlying set content.
		/// </summary>
		protected IList<T> Elements
		{
			get
			{
				return this.elements;
			}
			set
			{
				this.elements = value;
			}
		}

		/// <summary>
		/// Gets or sets the action to take if a duplicate item is about to be added to the collection.
		/// </summary>
		protected DuplicateAction DuplicateAction
		{
			get
			{
				return this.duplicateAction;
			}
			set
			{
				this.duplicateAction = value;
			}
		}

		/// <summary>
		/// Creates a new <see cref="BoxedCollection{T}"/> instance.
		/// </summary>
		public BoxedCollection()
		{
			this.elements = new List<T>();
		}

		/// <summary>
		/// Creates a new <see cref="BoxedCollection{T}"/> instance.
		/// </summary>
		/// <param name="elements">The underlying (boxed) collection</param>
		public BoxedCollection(IList<T> elements)
			: this()
		{
			this.elements = elements;
		}

		/// <summary>
		/// Validates the given item before adding or inserting to the collection. Overwrite in user-defined 
		/// collection types to implement own validation logic.
		/// </summary>
		/// <remarks>This method makes use of the <see cref="DuplicateAction"/> property to decide whether an exception is thrown.</remarks>
		/// <param name="item">The item to be validated</param>
		/// <returns>True if the item is valid, false otherwise</returns>
		/// <exception cref="ArgumentNullException">The specified item contains a null-reference.</exception>
		/// <exception cref="ArgumentException">The specified item is already contained in the collection.</exception>
		protected virtual bool ValidateItem(T item)
		{
			if (item == null)
				throw new ArgumentNullException("item");
			if (this.Contains(item))
			{
				if (this.DuplicateAction == DuplicateAction.ThrowException)
					throw new ArgumentException("The specified item is already contained in the collection.", "item");
				else if (this.DuplicateAction == DuplicateAction.Ignore)
					return false;
			}
			return true;
		}

		private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
		{
			if (this.collectionChanged != null)
				this.collectionChanged(this, e);
		}

		#region IList<T> Members

		/// <see cref="IList&lt;T&gt;.IndexOf(T)"/>
		public virtual int IndexOf(T item)
		{
			return this.elements.IndexOf(item);
		}

		/// <see cref="IList&lt;T&gt;.Insert(int, T)"/>
		public virtual void Insert(int index, T item)
		{
			if (!this.ValidateItem(item))
				return;
			this.elements.Insert(index, item);
			this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(
				NotifyCollectionChangedAction.Add, item, index));
		}

		/// <see cref="IList&lt;T&gt;.RemoveAt(int)"/>
		public virtual void RemoveAt(int index)
		{
			this.elements.RemoveAt(index);
			this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove));
		}

		/// <summary>
		/// Gets or sets an element of this set.
		/// </summary>
		/// <param name="index">The zero-based index</param>
		/// <returns>The set element of type <typeparamref name="T"/></returns>
		public virtual T this[int index]
		{
			get
			{
				return this.elements[index];
			}
			set
			{
				if (!this.ValidateItem(value))
					return;
				this.elements[index] = value;
				this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(
					NotifyCollectionChangedAction.Replace, value, index));
			}
		}

		#endregion

		#region ICollection<T> Members

		/// <see cref="ICollection&lt;T&gt;.Add(T)"/>
		public virtual void Add(T item)
		{
			if (!this.ValidateItem(item))
				return;
			this.elements.Add(item);
			this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(
				NotifyCollectionChangedAction.Add, item));
		}

		/// <see cref="ICollection&lt;T&gt;.Clear"/>
		public virtual void Clear()
		{
			this.elements.Clear();
			this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
		}

		/// <see cref="ICollection&lt;T&gt;.Contains(T)"/>
		public virtual bool Contains(T item)
		{
			return this.elements.Contains(item);
		}

		/// <see cref="ICollection&lt;T&gt;.CopyTo(T[], int)"/>
		public virtual void CopyTo(T[] array, int arrayIndex)
		{
			this.elements.CopyTo(array, arrayIndex);
		}

		/// <see cref="ICollection&lt;T&gt;.Count"/>
		public int Count
		{
			get { return this.elements.Count; }
		}

		/// <see cref="ICollection&lt;T&gt;.IsReadOnly"/>
		public virtual bool IsReadOnly
		{
			get { return this.elements.IsReadOnly; }
		}

		/// <see cref="ICollection&lt;T&gt;.Remove(T)"/>
		public virtual bool Remove(T item)
		{
			bool result = this.elements.Remove(item);
			if (result)
			{
				this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(
					NotifyCollectionChangedAction.Remove, item));
			}
			return result;
		}

		#endregion

		#region IEnumerable<T> Members

		/// <see cref="IEnumerable&lt;T&gt;.GetEnumerator"/>
		public System.Collections.Generic.IEnumerator<T> GetEnumerator()
		{
			return this.elements.GetEnumerator();
		}

		#endregion

		#region IEnumerable Members

		/// <see cref="IEnumerable.GetEnumerator"/>
		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return ((IEnumerable)this.elements).GetEnumerator();
		}

		#endregion

		#region INotifyCollectionChanged Members

		/// <see cref="INotifyCollectionChanged.CollectionChanged"/>
		public event NotifyCollectionChangedEventHandler CollectionChanged
		{
			add
			{
				this.collectionChanged += value;
			}
			remove
			{
				this.collectionChanged -= value;
			}
		}

		#endregion
	}
}
